package com.fzk.controller;

import com.alibaba.fastjson2.JSON;
import com.fzk.router.ApiHandler;
import com.fzk.router.AutoRegister;
import com.fzk.router.MyCtx;
import com.fzk.router.MyRequestMapping;
import com.fzk.server.HttpResponseUtil;
import com.fzk.server.MyHttpUtil;
import com.fzk.utils.MyCollectionUtil;
import com.fzk.utils.MyHmac;
import com.fzk.utils.MyToken;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.Set;

/**
 * @author uestcfzk
 * @date 2025-02-22 22-25-11
 */
@SuppressWarnings("unused")
@AutoRegister
public class LoginController {
    private static final String passwordMd5 = "448d65ad1006325e1c509ca1fc0ade1b";
    public static final long tokenExpireTimeMills = 1000 * 60 * 60 * 24L;// 过期时间-毫秒级
    public static final String tokenPayLoadIPName = "cliAddr";
    public static final String loginApiUri = "/fs/api/login";

    @MyRequestMapping(path = loginApiUri, method = {"POST"})
    private final ApiHandler login = (ctx, request, params) -> {
        if (MyCollectionUtil.allNotEmpty(params.get("username"), params.get("username"))) {
            String username = params.get("username").get(0);
            String password = params.get("password").get(0);
            ctx.info(String.format("try login username: %s, password: %s", username, password));
            if ("root".equals(username) && !password.isEmpty() && MyHmac.md5(password.getBytes(StandardCharsets.UTF_8)).equals(passwordMd5)) {
                ctx.info(String.format("login successful, username: %s, password: %s", username, password));
                // 登录成功，设置cookie，重定向到首页
                FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND, Unpooled.EMPTY_BUFFER);
                // 将 cookie 与用户 IP、User-Agent 等信息绑定，增加劫持难度
                MyToken token = new MyToken(tokenExpireTimeMills).
                        addPayload("username", username).
                        addPayload(tokenPayLoadIPName, MyHttpUtil.getClientAddress(ctx.getChannelCtx(), request)).
                        addPayload(HttpHeaderNames.USER_AGENT.toString(), request.headers().get(HttpHeaderNames.USER_AGENT));
                DefaultCookie c = new DefaultCookie(HttpHeaderNames.AUTHORIZATION.toString(), token.toTokenString());
                c.setPath(IndexController.rootPageUri);// 只有当前服务可见
                c.setMaxAge(tokenExpireTimeMills / 1000L);
                response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.LAX.encode(c));
                HttpResponseUtil.sendPageRedirect(ctx.getChannelCtx(), request, response, IndexController.indexPageUri);
                return;
            }
        }
        HttpResponseUtil.sendError(ctx.getChannelCtx(), HttpUtil.isKeepAlive(request), HttpResponseStatus.BAD_REQUEST, "invalid username or password");
    };

    /**
     * 校验一个请求的token是否合法
     * 包括但不限于过期时间、签名、绑定信息
     * 将 cookie 与用户 IP、User-Agent 等信息绑定，增加劫持难度
     *
     * @return 是否合法
     */
    public static boolean validateToken(MyCtx ctx, FullHttpRequest request) {
        // 1.从cookie取出token
        String tokenVal = null;
        String cookie = request.headers().get(HttpHeaderNames.COOKIE);
        if (StringUtils.isNotEmpty(cookie)) {
            Set<io.netty.handler.codec.http.cookie.Cookie> cookies = ServerCookieDecoder.LAX.decode(cookie);
            for (Cookie c : cookies) {
                if (HttpHeaderNames.AUTHORIZATION.toString().equalsIgnoreCase(c.name())) {
                    tokenVal = c.value();
                    break;
                }
            }
        }
        // 2.校验token自身值合法性: 过期时间和签名
        MyToken token = MyToken.validate(tokenVal);
        if (token != null) {
            // 3.校验token绑定信息合法性
            // 将 cookie 与用户 IP、User-Agent 等信息绑定
            String bindUA = token.getPayload() == null ? null : (token.getPayload().get(HttpHeaderNames.USER_AGENT.toString()).toString());
            String ua = request.headers().get(HttpHeaderNames.USER_AGENT);
            String bindIp = token.getPayload() == null ? null : (token.getPayload().get(LoginController.tokenPayLoadIPName).toString());
            String cliAddr = MyHttpUtil.getClientAddress(ctx.getChannelCtx(), request);
            if (StringUtils.equals(ua, bindUA) && StringUtils.equals(cliAddr, bindIp)) {
                return true;
            }
            ctx.warning(String.format("validate token bind info failed, cookie hijacking may have occurred, token: %s, ua: %s, cliAddr: %s", JSON.toJSONString(token), ua, cliAddr));
        }
        ctx.info(String.format("validate token failed, tokenVal: %s", tokenVal));
        return false;
    }
}
